module render.renderer_ray;

import material.material;
import material.texture;
import objects.trianglemesh;
import render.render;
debug import objects.triangle;
import render.abstractrenderer;
import scene.camera;
import scene.ray;
import scene.intersection;
import scene.scene;
import shader.abstractshader;
import shader.environment;
import std.math;
import std.stdio;
import utils.vector2;
import utils.vector3;
import utils.color;

//version = RayDiffDebug;

class Renderer_Ray: AbstractRenderer {
    private Scene mScene;
    private Camera mCamera;
    private AbstractShader mLitShader;
    private Environment mEnvironment;
    private Texture mBumpMap;
    private Vector2f mPixelSize;
    private Vector2f mOutSize;

    public RGBColor ambientlight;
    public int recursiondepth = 0;
    public bool cameralight, first;
    public bool simpleReflect = false;

    public this() {
        ambientlight = RGBColor(1.0f, 0.0f, 0.0f);
    }

    public void initRender(Vector2f pixelSize, Vector2f validFrom,
        Vector2f validTo)
    {
        //mScene.buildBIH();
        debug g_IntersectCount = 0;
        mPixelSize = pixelSize;
        mOutSize = validTo - validFrom;
        first = true;
    }

    public void terminateRender() {
        //debug writefln("%d",g_IntersectCount);
    }

    public RGBColor render(Vector2f pos) {
        RGBColor ret;

        //transform coordinates from [0,1]^2 to [-0.5,0.5]^, inverted y
        pos.x = pos.x - mOutSize.x/2;
        pos.y = -pos.y + mOutSize.y/2;

        auto ray = mCamera.generateRay(pos);

        ret = castRay(ray);
        return ret;
    }

    public RGBColor castRay(Ray r, int depth = 0) {
        RGBColor res;
        RenderContext context;
        context.ray = r;

        context.currecdepth = depth;
        context.scene = mScene;
        if (!(simpleReflect && depth > 0) && (mScene.intersect(context.ray, context.rinters))) {
            //intersect has already set all intersection-relevant values here

            //modify intersection normal according to bumpmap
            if (mBumpMap)
                doBumpMap(context);

            res = shade(context);
        } else {
            res = mEnvironment.getColor(context.ray.dir);
            //col.r = col.g = col.b = 0.5f;
        }
        return res;
    }

    private RGBColor shade(inout RenderContext context) {
        RGBColor res;

        context.rinters.deltaTx = (1.0f/mPixelSize.x)*context.rinters.rdif_dTx;
        context.rinters.deltaTy = (1.0f/mPixelSize.y)*context.rinters.rdif_dTy;

        if (first) {
            //std.stdio.writefln("%s %s",context.rinters.deltaTx.length,context.rinters.deltaTx.length);
            first = false;
        }

        version(RayDiffDebug) {
            float lod = log2(fmax(10*context.rinters.deltaTx.length,10*context.rinters.deltaTy.length));
            lod = fmax(0,lod);
            res = RGBColor(lod);
            return res;
        }

        //NOTE: context.color is (0,0,0), as it should be

        if (mLitShader)
            mLitShader.getColor(context);

        //recursion for reflection
        if (context.currecdepth < recursiondepth) {
            auto n = (context.ray.dir - 2.0f*(context.rinters.normal*context.ray.dir)*context.rinters.normal).normal;
            Ray reflectray = Ray(context.rinters.hitpoint+0.01f*context.rinters.normal, n, float.max);

            reflectray.rdif_dPx = context.ray.rdif_dPx;
            reflectray.rdif_dPy = context.ray.rdif_dPy;

            float dDndx = context.ray.rdif_dDx*context.rinters.normal + context.ray.dir*context.rinters.rdif_dNx;
            float dDndy = context.ray.rdif_dDy*context.rinters.normal + context.ray.dir*context.rinters.rdif_dNy;

            reflectray.rdif_dDx = context.ray.rdif_dDx - 2.0f*(
                (context.ray.dir*context.rinters.normal)*context.rinters.rdif_dNx +
                dDndx*context.rinters.normal);
            reflectray.rdif_dDy = context.ray.rdif_dDy - 2.0f*(
                (context.ray.dir*context.rinters.normal)*context.rinters.rdif_dNy +
                dDndy*context.rinters.normal);

            RGBColor rec = castRay(reflectray, context.currecdepth+1);
            context.addDirectionalLight(rec, reflectray.dir);
        }

        //it's too dark here! where's the light switch?
        context.addAmbientLight(ambientlight);

        //context.ray.rdif_P_x

        //hacky camera light
        if (cameralight) {
            context.addDirectionalLight(RGBColor(1.0f, 1.0f, 1.0f), -context.ray.dir.normal);
        }

        res = context.color;
        return res;
    }

    private void doBumpMap(inout RenderContext context){
        //assuming greyscale
        float col_x0 = mBumpMap.getColor(context,Vector2i(1,0)).r;
        float col_x1 = mBumpMap.getColor(context,Vector2i(-1,0)).r;

        float col_y0 = mBumpMap.getColor(context,Vector2i(0,1)).r;
        float col_y1 = mBumpMap.getColor(context,Vector2i(0,-1)).r;

        float bumpU = col_x0 - col_x1;
        float bumpV = col_y0 - col_y1;

        Vector3f vUBasis = Vector3f(0, 1, 0);
        Vector3f vVBasis = Vector3f(1, 0, 0);

        Vector3f vTemp = context.rinters.normal | (vUBasis * bumpU * 5) -
            context.rinters.normal | (vVBasis * bumpV * 5);
        //herrgottnochmalverdammtecompilerbugs
        context.rinters.normal = (context.rinters.normal + vTemp).normal();
    }

    public void scene(Scene sc) {
        mScene = sc;
    }

    public void camera(Camera cam) {
        mCamera = cam;
    }

    public void lightingShader(AbstractShader sh) {
        mLitShader = sh;
        /*if (sh)
            mLitShader.scene = mScene;*/
    }
    public AbstractShader lightingShader() {
        return mLitShader;
    }

    public void environment(Environment env) {
        mEnvironment = env;
    }

    public Texture bumpMap() {
        return mBumpMap;
    }
    public void bumpMap(Texture val) {
        mBumpMap = val;
    }

    public Vector2f renderSize(float outAr) {
        return Vector2f(1.0f*outAr, 1.0f);
    }
}
